As
you can probably imagine, if you had to draw everything inside a single
class, your code would quick become a mess of things to keep track of!
GameComponents
Luckily, the framework
provides an easy way to encapsulate objects called game components.
There are three types of game components you can use for your games: GameComponent, DrawableGameComponent, and GamerServicesComponent. The latter is discussed later, but let’s dive into the others now.
First, you want to take the
image-bouncing code you wrote and move it into a component, so in your
main game project, right-click the project and select Add -> New
Item. Choose Game Component from the list of templates (it might be
easier to find if you choose the XNA Game Studio 4.0 section in the
list on the left), name it BouncingImage.cs, and then click the Add
button.
This adds a new file to your project with a new class deriving from GameComponent,
which is close to what you want but not quite. Open up BouncingImage.cs
(it should have opened when you added the component), and change it to
derive from DrawableGameComponent instead:
public class BouncingImage : Microsoft.Xna.Framework.DrawableGameComponent
Now you can begin moving the code you used in your Game class to render your bouncing image to this component. Start by moving the three variables you added to the new BouncingImage class (texture, position, and velocity). Move the code initializing your two vectors into the new classes Initialize method and move the code were you modify the vectors in update to the new classes Update method. You need to do just a few things to complete your bouncing image component.
You need a way to load the texture, and DrawableGameComponent has the same virtual LoadContent method that the game has, so you can simply override it in your BouncingImage class now:
protected override void LoadContent()
{
texture = Game.Content.Load<Texture2D>("XnaLogo");
base.LoadContent();
}
Finally, all you need now is to draw the image. Just like LoadContent, DrawableGameComponent also has a Draw virtual method you can override:
public override void Draw(GameTime gameTime)
{
spriteBatch.Begin();
spriteBatch.Draw(texture, position, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
As you might have seen already, this won’t compile. The spriteBatch
variable is declared in the game, and it is a private member variable.
You can create a new sprite batch for this component, but it isn’t
necessary. If you remember back to earlier in this chapter, we talked
about the Services property on the Game class.
Go back to the game1.cs code file and to the LoadContent method. Replace the loading of the texture (which you just did in the BouncingImage class) with the following line (directly after the sprite batch has been created):
Services.AddService(typeof(SpriteBatch), spriteBatch);
This adds a “service” of type SpriteBatch
to the game class and uses the just created sprite batch as the
provider of that service. This enables you to use the “service” from
anything that has access to the game. Back in the BouncingImage.Draw method, before the Begin call, add the following:
SpriteBatch spriteBatch = Game.Services.GetService(
typeof(SpriteBatch)) as SpriteBatch;
Now that you have your drawing code in your component compiling, you can remove it from the Draw call in the game. It should have nothing but the Clear call and the base.Draw
call now. With everything compiling, you can run your project and you
will see absolutely nothing except a blank cornflower blue screen. This
is because your component was never actually used! Back in the Initialize method in the game, add the following:
Components.Add(new BouncingImage(this));
That’s it. Running the code
gets you back to where you were before, but with everything much more
encapsulated. It’s also much easier to add new instances of the
bouncing image; for example, add this to your games Update method:
if ( ((int)gameTime.TotalGameTime.TotalSeconds % 5) == 4)
Components.Add(new BouncingImage(this));
Running it now adds a new set
of bouncing images every 5 seconds (actually it adds quite a few
because it adds one for every update that happens during that second).
You can go ahead and remove that code; it is just an example of how
easy it is to include more.
You might have noticed that you didn’t actually have to do anything outside of add your component to the Components collection for it to start working magically. Your Initialize method is called for you, as is your LoadContent method, and the Update and Draw methods were called for each frame.
By default, components are
updated and drawn in the order they were added, and they are always
updated and drawn. However, these are all changeable, too. If you set
the DrawOrder property, when the components are being drawn, the components with the lower DrawOrder values are drawn first. Similarly, if you use the UpdateOrder property, the components with the lower UpdateOrder
value are updated first. The higher value these properties have, the
later they happen in the list of components. If you want something
drawn as the last possible component, you set the DrawOrder to int.MaxValue, for example. Of course, if you have more than one component with the same UpdateOrder or DrawOrder, they are called in the order they were added.
Of course, there might
be times when you don’t want your component to be drawn at all! If this
is the case, you can simply set the Visible
property to false, and your Draw override is no longer called until
that property switches back to true. If you need to temporarily suspend
updating for a while, you can just change the Enabled property to false!
There are also events (and
virtual methods) to notify you when any of these properties change if
you need to know about the changes.
Note
The GameComponent class has the same behavior as the DrawableGameComponent class without the extra methods and properties used for Drawing, such as LoadContent and DrawOrder.